////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNScripting/inc/WNDataType.h"
#include "WNScripting/inc/WNExpression.h"
#include "WNScripting/inc/WNLLVMHelpers.h"

using namespace WNScripting;

WNDataType::WNDataType(WN_SIZE_T _startLine,
                WN_SIZE_T _endLine,
                WN_SIZE_T _startChar,
                WN_SIZE_T _endChar,
                WNVariableType _type) :
    WNParseNode(_startLine, _endLine, _startChar, _endChar),
    mType(_type),
    mName(WNInvalidIdentifier),
    mElementType(WNInvalidType),
    mLength(WN_NULL),
    mLengthVal(WN_NULL) {
}

WNDataType::WNDataType(WN_SIZE_T _startLine,
                       WN_SIZE_T _endLine,
                       WN_SIZE_T _startChar,
                       WN_SIZE_T _endChar,
                       WNIdentifierElement _name) :
    WNParseNode(_startLine, _endLine, _startChar, _endChar),
    mType(eWNTypeUser),
    mName(_name),
    mLength(WN_NULL),
    mLengthVal(WN_NULL) {
}

WNDataType::WNDataType(WN_SIZE_T _startLine,
                       WN_SIZE_T _endLine,
                       WN_SIZE_T _startChar,
                       WN_SIZE_T _endChar,
                       WNDataType* _subType) :
    WNParseNode(_startLine, _endLine, _startChar, _endChar),
    mType(eWNTypeArray),
    mSubType(_subType),
    mLength(WN_NULL),
    mLengthVal(WN_NULL) {
}

WNDataType::WNDataType(WN_SIZE_T _startLine,
                       WN_SIZE_T _endLine,
                       WN_SIZE_T _startChar,
                       WN_SIZE_T _endChar,
                       WNDataType* _subType,
                       WNExpression* _length) :
    WNParseNode(_startLine, _endLine, _startChar, _endChar),
    mType(eWNTypeArray),
    mSubType(_subType),
    mLength(_length),
    mLengthVal(WN_NULL) {
}

llvm::Value* WNDataType::OptimizeAndGenerate(WNScriptingEngineData& _scriptingData, WNFunctionDecl* _functionDecl){
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_scriptingData.mBuilder);

    WNParseNode::OptimizeAndGenerate(_scriptingData, _functionDecl);

    if (mType == eWNTypeArray) {
        mOptimizationList.push_back(this);
        mSubType->OptimizeAndGenerateArray(_scriptingData, _functionDecl, mOptimizationList);
        mElementType = _scriptingData.mTypeManager->GetArrayType(mSubType->mElementType);

        if (mLength != WN_NULL) {
            mLengthVal = mLength->OptimizeAndGenerate(_scriptingData, _functionDecl);
            if (mLengthVal != WN_NULL) {
                WNGenerateLLVMCast castOp = _scriptingData.mTypeManager->GetLLVMCastGenerator(_scriptingData.mTypeManager->GetType(eWNTypeInt), mLength->GetType());

                if (castOp == WN_NULL) {
                    mFail = "Cannot convert array bounds to integer type";
                    return(WN_NULL);
                }
                mLengthVal = builder->CreateIntCast(mLengthVal, _scriptingData.mTypeManager->GetIntPtrType(), WN_FALSE);
            }
        }
    } else {
        mElementType = _scriptingData.mTypeManager->GetType(mType, mName);
    }
    return(WN_NULL);
}

llvm::Value* WNDataType::OptimizeAndGenerateArray(WNScriptingEngineData& _scriptingData, WNFunctionDecl* _functionDecl, std::list<WNDataType*>& _dataTypes) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_scriptingData.mBuilder);

    WNParseNode::OptimizeAndGenerate(_scriptingData, _functionDecl);

    if (mType == eWNTypeArray) {
        if(mLength != WN_NULL) {
            mLengthVal = mLength->OptimizeAndGenerate(_scriptingData, _functionDecl);

            if (mLengthVal != WN_NULL) {
                WNGenerateLLVMCast castOp = _scriptingData.mTypeManager->GetLLVMCastGenerator(_scriptingData.mTypeManager->GetType(eWNTypeInt), mLength->GetType());

                if (castOp == WN_NULL) {
                    mFail = "Cannot convert array bounds to integer type";

                    return(WN_NULL);
                }

                mLengthVal = builder->CreateIntCast(mLengthVal, _scriptingData.mTypeManager->GetIntPtrType(), WN_FALSE);
            }
        }

        _dataTypes.push_front(this);
        mSubType->OptimizeAndGenerateArray(_scriptingData, _functionDecl, _dataTypes);
        mElementType = _scriptingData.mTypeManager->GetArrayType(mSubType->mElementType);
    } else {
        mElementType = _scriptingData.mTypeManager->GetType(mType, mName);
    }

    return(WN_NULL);
}

WNTypeElement WNDataType::GetType() {
    if (!mOptimized) {
        mFail = "You cannot get a type on a DataType without optimizing it";
    }

    return(mElementType);
}

llvm::Value* WNDataType::GetOptimizedLength() {
    return(mLengthVal);
}

WNDataType* WNDataType::GetSubType() {
    return(mSubType);
}

const std::list<WNDataType*>* WNDataType::GetSubTypeArray() {
    return(&mOptimizationList);
}

WNIdentifierElement WNDataType::GetName() {
    return(mName);
}